﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Documents;
using ObjectSearch.Configuration;
using System.ComponentModel;
using System.Reflection;
using System.Collections;

namespace ObjectSearch.Converter
{
    public class DocumentToObjectConverter
    {
        public static object Convert(Document doc, Type objectTypeToCreate, Configuration.Configuration configuration)
        {
            object indexedObject = Activator.CreateInstance(objectTypeToCreate);
            
            ObjectMetaData meta = configuration.FindMetaData(objectTypeToCreate);

            foreach (FieldMetaData fieldMeta in meta.Fields)
            {
                object value = GetValue(doc, objectTypeToCreate, fieldMeta.MemberName, configuration);
                SetMemberValue(indexedObject, fieldMeta.MemberName, value);
            }

            return indexedObject;
        }

        public static object GetValue(Document doc, Type objectType, string memberName, Configuration.Configuration configuration)
        {
            ObjectMetaData meta = configuration.FindMetaData(objectType);
            FieldMetaData fieldMeta = meta.FindByObjectMemberName(memberName);
            string fieldName = fieldMeta.FieldName;

            Field f = doc.GetField(fieldName);

            if (f.IsBinary())
            {
                return f.BinaryValue();
            }

            //look for a type convert on the property itself.
            TypeConverter customConverter = GetTypeConverterFromAttribute(objectType, memberName);

            if (customConverter != null)
            {
                object convertedObject = customConverter.ConvertFromString(f.StringValue());
                return convertedObject;
            }

            //else check for date
            if (GetMemberType(objectType, memberName) == typeof(DateTime))
            {
                string stringValue = f.StringValue();
                DateTime value = DateTools.StringToDate(f.StringValue());
                //DateTime value = DateField.StringToDate(field.StringValue());
                return value;
            }

            //use default type converter
            TypeConverter standardTypeConverter = TypeDescriptor.GetConverter(GetMemberType(objectType, memberName));

            //if the standard convert is null throw exception
            if (standardTypeConverter == null)
                throw new LuceneException(string.Format("Cannot determin how to return the value of the object of type {0}, type using the TypeConvertAttribute on the {1} property to fix this issue.", GetMemberType(objectType, memberName), memberName));

            //use the type converter
            object convertedValue = standardTypeConverter.ConvertFromString(f.StringValue());
            return convertedValue;
        }

        private static TypeConverter GetTypeConverterFromAttribute(Type objectType, string memberName)
        {
            PropertyInfo propertyInfo = objectType.GetProperty(memberName);
            FieldInfo fieldInfo = objectType.GetField(memberName);

            if (propertyInfo != null)
            {
                object[] customAttributes = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), true);

                if (customAttributes.Length > 0)
                {
                    TypeConverterAttribute attribute = customAttributes[0] as TypeConverterAttribute;
                    TypeConverter converter = Activator.CreateInstance(Type.GetType(attribute.ConverterTypeName)) as TypeConverter;
                    return converter;
                }
            }

            if (fieldInfo != null)
            {
                object[] customAttributes = fieldInfo.GetCustomAttributes(typeof(TypeConverterAttribute), true);

                if (customAttributes.Length > 0)
                {
                    TypeConverterAttribute attribute = customAttributes[0] as TypeConverterAttribute;
                    TypeConverter converter = Activator.CreateInstance(Type.GetType(attribute.ConverterTypeName)) as TypeConverter;
                    return converter;
                }
            }

            return null;
        }
        private static Type GetMemberType(Type objectType, string memberName)
        {
            PropertyInfo propertyInfo = objectType.GetProperty(memberName);
            FieldInfo fieldInfo = objectType.GetField(memberName);

            if (propertyInfo != null)
            {
                return propertyInfo.PropertyType;
            }

            if (fieldInfo != null)
            {
                return fieldInfo.FieldType;
            }

            return null;
        }

        private static void SetMemberValue(object containingObject, string memberName, object value)
        {
            Type objectType = containingObject.GetType();

            PropertyInfo propertyInfo = objectType.GetProperty(memberName);

            if (propertyInfo != null)
            {
                propertyInfo.SetValue(containingObject, value, null);
                return;
            }

            FieldInfo fieldInfo = objectType.GetField(memberName);

            if (fieldInfo != null)
            {
                fieldInfo.SetValue(containingObject, value);
                return;
            }

            throw new LuceneException("Attempt to set a members value on a property or field  that does not exists.");
        }

    }
}
